import Blocks from "@/components/blocks"; import { fetchSanityPageBySlug, PAGES_SLUGS_QUERY } from "@/sanity/lib/fetch"; import { notFound, permanentRedirect } from "next/navigation"; import { generatePageMetadata } from "@/sanity/lib/metadata"; import { getDynamicFetchOptions, sanityFetchMetadata, sanityFetchStaticParams, type DynamicFetchOptions, } from "@/sanity/lib/live"; import { PAGE_QUERY_RESULT, PAGES_SLUGS_QUERY_RESULT } from "@/sanity.types"; import { PAGE_QUERY } from "@/sanity/queries/page"; import { groq } from "next-sanity"; import { draftMode } from "next/headers"; import { Suspense } from "react"; function PageFallback() { return (
); } const RESERVED_STATIC_SLUGS = new Set([ "index", "about", "services", "contact", "partnership", "blog", ]); const KNOWN_POST_SLUGS = new Set([ "vietnam-wedding-guide-part-1", "why-vietnam", "wedding-framework-project-managment", "ngan-sach-tiec-cuoi-cao-cap-viet-nam-phan-1", "ngan-sach-tiec-cuoi-cao-cap-viet-nam-phan-2", "tai-sao-chon-viet-nam", "chn-la-wedding-planner-ch-c-ph-hp-khng-c-tt-nht", "wedding-in-vietnam-questions", ]); export async function generateStaticParams() { const { data: pages } = (await sanityFetchStaticParams({ query: PAGES_SLUGS_QUERY, })) as { data: PAGES_SLUGS_QUERY_RESULT }; const slugs = (pages ?? []).flatMap((page) => { const slug = page.slug?.current; if (!slug) { return []; } const pathParts = slug.split("/").filter(Boolean); // Exclude dedicated static routes to eliminate routing collisions if (pathParts.length === 1 && RESERVED_STATIC_SLUGS.has(pathParts[0])) { return []; } return [{ slug: pathParts }]; }); // Next.js 16 Cache Components requires generateStaticParams to return at least 1 item. // Use a non-colliding placeholder slug so it never conflicts with real static routes like /about. if (slugs.length === 0) { return [{ slug: ["_cms-preview"] }]; } return slugs; } export async function generateMetadata(props: { params: Promise<{ slug: string[] }>; }) { const [{ slug }, { perspective }] = await Promise.all([ props.params, getDynamicFetchOptions(), ]); const slugPath = slug.join("/"); const { data: page } = (await sanityFetchMetadata({ query: PAGE_QUERY, params: { slug: slugPath }, perspective, })) as { data: PAGE_QUERY_RESULT }; if (!page) { if (slug.length === 1) { const singleSlug = slug[0]; if (KNOWN_POST_SLUGS.has(singleSlug)) { permanentRedirect(`/blog/${singleSlug}`); } try { const { data: hasPost } = (await sanityFetchMetadata({ query: groq`count(*[_type == "post" && slug.current == $slug]) > 0`, params: { slug: singleSlug }, perspective, })) as { data: boolean }; if (hasPost) { permanentRedirect(`/blog/${singleSlug}`); } } catch (e) { // ignore } } notFound(); } return generatePageMetadata({ page, slug: slugPath }); } export default async function Page(props: { params: Promise<{ slug: string[] }>; }) { const { isEnabled: isDraftMode } = await draftMode(); if (isDraftMode) { return ( }> ); } const { slug } = await props.params; return ( ); } async function DynamicPage({ params, }: { params: Promise<{ slug: string[] }>; }) { const [{ slug }, { perspective, stega }] = await Promise.all([ params, getDynamicFetchOptions(), ]); return ( ); } async function CachedPage({ slug, perspective, stega, }: { slug: string } & DynamicFetchOptions) { const page = await fetchSanityPageBySlug({ slug, perspective, stega }); if (!page) { const pathParts = slug.split("/").filter(Boolean); if (pathParts.length === 1) { const singleSlug = pathParts[0]; if (KNOWN_POST_SLUGS.has(singleSlug)) { permanentRedirect(`/blog/${singleSlug}`); } try { const { data: hasPost } = (await sanityFetchMetadata({ query: groq`count(*[_type == "post" && slug.current == $slug]) > 0`, params: { slug: singleSlug }, perspective, })) as { data: boolean }; if (hasPost) { permanentRedirect(`/blog/${singleSlug}`); } } catch (e) { // ignore } } notFound(); } return ( ); }